package git.soulbgm.utils;

import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import org.apache.commons.collections4.ListUtils;
import org.apache.poi.ss.formula.functions.T;

import java.io.*;
import java.lang.reflect.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 使用依赖
 * <dependency>
 * <groupId>com.alibaba</groupId>
 * <artifactId>easyexcel</artifactId>
 * <version>1.1.2-beta5</version>
 * </dependency>
 *
 * @author SoulBGM
 * @version 1.0
 * @date 2019/5/30 0:01
 */
public class ExcelUtil {

    /**
     * 创建文件
     *
     * @param file File对象
     * @return 创建成功true否则false
     */
    public static boolean createFile(File file) {
        if (file == null) {
            return false;
        }
        if (exist(file)) {
            return true;
        }
        File parentFile = file.getParentFile();
        boolean flag = false;
        if (parentFile != null) {
            if (exist(parentFile)) {
                flag = true;
            } else {
                flag = parentFile.mkdirs();
            }
            if (flag) {
                try {
                    return file.createNewFile();
                } catch (IOException e) {
                    return false;
                }
            }
        }
        return false;
    }

    /**
     * 文件是否存在
     *
     * @param file File对象
     * @return 存在true否则false
     */
    public static boolean exist(File file) {
        if (file != null) {
            return file.exists();
        }
        return false;
    }

    private static final String GET = "get";
    private static final String IS = "is";
    private static final String SET = "set";

    /**
     * 通过给字段名称构建出相应的get方法名称
     *
     * @param fieldName 字段名称
     * @return 组合之后的名称
     */
    private static String buildGetMethod(String fieldName, boolean isBoolean) {
        StringBuilder sb = new StringBuilder();
        if (isBoolean) {
            sb.append(IS);
        } else {
            sb.append(GET);
        }
        sb.append(Character.toUpperCase(fieldName.charAt(0)));
        sb.append(fieldName.substring(1));
        return sb.toString();
    }

    /**
     * 通过给字段名称构建出相应的set方法名称
     *
     * @param fieldName 字段名称
     * @return 组合之后的名称
     */
    private static String buildSetMethod(String fieldName) {
        StringBuilder sb = new StringBuilder(SET);
        sb.append(Character.toUpperCase(fieldName.charAt(0)));
        sb.append(fieldName.substring(1));
        return sb.toString();
    }

    /**
     * 通过反射的方式执行getXxx()方法
     *
     * @param obj   实体对象
     * @param field 字段
     * @return 执行了getXxx()方法之后的值
     */
    public static Object executeGetMethod(Object obj, Field field) {
        if (obj == null) {
            return null;
        }
        try {
            return obj.getClass().getMethod(buildGetMethod(field.getName(), field.getType() == Boolean.class)).invoke(obj);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            return null;
        }
    }

    /**
     * 通过反射的方法执行setXxx(xx)方法
     *
     * @param obj   实体对象
     * @param field 字段
     * @param val   执行set方法的值
     * @return 成功返回true，失败false
     */
    public static boolean executeSetMethod(Object obj, Field field, Object val) {
        if (obj == null) {
            return false;
        }
        try {
            Method method = obj.getClass().getMethod(buildSetMethod(field.getName()), field.getType());
            method.invoke(obj, val);
            return true;
        } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            return false;
        }
    }

    private final static String FORMAT = "yyyy-MM-dd HH:mm:ss";

    /**
     * 字符串转各种类型
     *
     * @param str   字符串
     * @param clazz 类型的class
     * @return 符合clazz的对象
     */
    public static Object strToObj(String str, Class clazz) {
        if (clazz == null || str == null) {
            return null;
        }
        if (clazz == byte.class || clazz == Byte.class) {
            return Byte.parseByte(str);
        } else if (clazz == short.class || clazz == Short.class) {
            return Short.parseShort(str);
        } else if (clazz == int.class || clazz == Integer.class) {
            return Integer.parseInt(str);
        } else if (clazz == long.class || clazz == Long.class) {
            return Long.parseLong(str);
        } else if (clazz == float.class || clazz == Float.class) {
            return Float.parseFloat(str);
        } else if (clazz == double.class || clazz == Double.class) {
            return Double.parseDouble(str);
        } else if (clazz == boolean.class || clazz == Boolean.class) {
            return Boolean.parseBoolean(str);
        } else if (clazz == char.class || clazz == Character.class) {
            return str.charAt(0);
        } else if (clazz == char[].class || clazz == Character[].class) {
            return str.toCharArray();
        } else if (clazz == Date.class) {
            SimpleDateFormat sdf = new SimpleDateFormat(FORMAT);
            try {
                return sdf.parse(str);
            } catch (ParseException e) {
                return null;
            }
        }
        return str;
    }

    private static final int MAX_LENGTH = 1000000;

    private static final String XLSX = ".xlsx";

    /**
     * 通过某个不为null的类来获取头信息
     *
     * @param obj 可以是实体 也可以是Map
     * @return 如果是实体返回实体的field 如果是Map返回key
     */
    public static <T> List<List<String>> getHeadList(T obj) {
        List<List<String>> headList = new ArrayList<>();
        if (obj == null) {
            return headList;
        }
        Class<T> clazz = (Class<T>) obj.getClass();
        if (clazz == Map.class || clazz == HashMap.class || clazz == LinkedHashMap.class) {
            Map objMap = (Map) obj;
            Set<String> set = objMap.keySet();
            for (String s : set) {
                headList.add(Arrays.asList(s));
            }
        } else {
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                headList.add(Arrays.asList(field.getName()));
            }
        }
        return headList;
    }

    /**
     * 通化传入head名称获得头信息的格式
     *
     * @param nameList 头名称
     * @return 返回工具类需要的头信息
     */
    public static <T> List<List<String>> getHeadList(List<String> nameList) {
        List<List<String>> headList = new ArrayList<>();
        if (nameList == null) {
            return headList;
        }
        for (String s : nameList) {
            headList.add(Arrays.asList(s));
        }
        return headList;
    }

    /**
     * 获得easyexcel需要的List<List<String>>类型
     *
     * @param data 需要生成Excel的数据
     * @return easyexcel需要的list类型
     */
    private static <T> List<List<String>> getDataList(List<T> data) {
        List<List<String>> dataList = new ArrayList<>();
        if (data == null) {
            return dataList;
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        for (T t : data) {
            Class<T> clazz = (Class<T>) t.getClass();
            if (t instanceof Map) {
                Map objMap = (Map) t;
                Set<String> set = objMap.keySet();
                List<String> list = new ArrayList<>();
                for (String s : set) {
                    Object o = objMap.get(s);
                    if (o.getClass() == Date.class) {
                        o = sdf.format(o);
                    }
                    list.add(String.valueOf(o));
                }
                dataList.add(list);
            } else {
                Field[] fields = clazz.getDeclaredFields();
                List<String> list = new ArrayList<>();
                for (Field field : fields) {
                    Object o = executeGetMethod(t, field);
                    if (o.getClass() == Date.class) {
                        o = sdf.format(o);
                    }
                    list.add(String.valueOf(o));
                }
                dataList.add(list);
            }
        }
        return dataList;
    }

    /**
     * 写Excel
     *
     * @param path      excel存放地址
     * @param excelName excel名称
     * @param dataList  数据
     * @return 成功true 失败false
     */
    public static <T> boolean writerExcel(String path, String excelName, List<T> dataList) {
        if (dataList == null || dataList.isEmpty()) {
            return false;
        }
        T t = dataList.get(0);
        return writerExcel(path, excelName, dataList, getHeadList(t));
    }

    /**
     * 写Excel
     *
     * @param path      excel存放地址
     * @param excelName excel名称
     * @param dataList  数据
     * @param headList  头列表
     * @return 成功true 失败false
     */
    public static <T> boolean writerExcel(String path, String excelName, List<T> dataList, List<List<String>> headList) {
        if (dataList == null || dataList.isEmpty()) {
            return false;
        }
        return baseWriterExcel(path, excelName, getDataList(dataList), headList);
    }

    /**
     * 写Excel
     *
     * @param path      excel存放地址
     * @param excelName excel名称
     * @param dataList  数据
     * @param headList  头列表
     * @return 成功true 失败false
     */
    public static boolean baseWriterExcel(String path, String excelName, List<List<String>> dataList, List<List<String>> headList) {
        if (dataList == null || dataList.isEmpty()) {
            return false;
        }
        Map<String, List> map = createPath(path, excelName, dataList);
        if (map == null) {
            return false;
        }
        Set<String> keySet = map.keySet();
        for (String filePath : keySet) {
            try {
                File file = new File(filePath);
                createFile(file);
                OutputStream out = new FileOutputStream(file);
                ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX);
                List data = map.get(filePath);
                Sheet sheet = new Sheet(1, 0, null, "Sheet1", headList);
                writer.write0(data, sheet);
                writer.finish();
                return true;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                return false;
            }
        }
        return false;
    }

    /**
     * 写Excel
     * 实体必须继承了BaseRowModel 并且字段上必须有@ExcelProperty这样的注解
     * ExcelProperty注解参数说明
     * value  表头值
     * index  列顺序(列顺序，越小越靠前)
     * format 日期格式化
     *
     * @param path      excel存放地址
     * @param excelName excel名称
     * @param dataList  数据
     * @return 成功true 失败false
     */
    public static boolean modelWriterExcel(String path, String excelName, List<? extends BaseRowModel> dataList, Class<? extends BaseRowModel> clazz) {
        if (dataList == null) {
            return false;
        }
        Map<String, List> map = createPath(path, excelName, dataList);
        if (map == null) {
            return false;
        }
        Set<String> keySet = map.keySet();
        for (String filePath : keySet) {
            try {
                File file = new File(filePath);
                createFile(file);
                OutputStream out = new FileOutputStream(file);
                ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX);
                Sheet sheet = new Sheet(1, 0, clazz, "Sheet1", null);
                writer.write(map.get(filePath), sheet);
                writer.finish();
                return true;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                return false;
            }
        }
        return false;
    }

    /**
     * 如果数据超过1000000那么需要进行多个excel存储
     *
     * @param path      excel存放地址
     * @param excelName excel名称
     * @param dataList  数据
     * @return key路径，value数据
     */
    public static Map<String, List> createPath(String path, String excelName, List dataList) {
        if (dataList == null) {
            return null;
        }
        int count = 1;
        int dataListSize = dataList.size();
        if (dataListSize >= MAX_LENGTH) {
            count = dataListSize % MAX_LENGTH == 0 ? dataListSize / MAX_LENGTH : dataListSize / MAX_LENGTH + 1;
        }
        Map<String, List> map = new HashMap<>(count);
        List<List> partitionList = ListUtils.partition(dataList, MAX_LENGTH);
        for (int i = 0; i < count; i++) {
            String filePath = count == 1 ? (path + File.separator + excelName + XLSX) : (path + File.separator + excelName + "_" + i + XLSX);
            map.put(filePath, partitionList.get(i));
        }
        return map;
    }

    /**
     * 读没有头的Excel
     *
     * @param filePath Excel文件地址
     * @return List<Map>对象Map的key和value的实际类型均为String
     */
    public static List<Map> readNoHeadExcel(String filePath) {
        return baseReadExcel(filePath, false, Map.class);
    }

    /**
     * 读Excel
     *
     * @param filePath Excel文件地址
     * @param clazz    对象的class
     * @return Excel数据
     */
    public static <T> List<T> readExcel(String filePath, Class<T> clazz) {
        return baseReadExcel(filePath, true, clazz);
    }

    /**
     * 读Excel
     *
     * @param filePath    Excel文件地址
     * @param isExistHead Excel文件是否有头
     * @param clazz       对象的class
     * @return Excel数据
     */
    public static <T> List<T> baseReadExcel(String filePath, boolean isExistHead, Class<T> clazz) {
        if (filePath == null) {
            return null;
        }
        InputStream is = null;
        try {
            is = new FileInputStream(filePath);
            ExcelListener<T> listener = new ExcelListener<>(isExistHead, clazz);
            ExcelReader reader = new ExcelReader(is, null, listener);
            List<Sheet> sheets = reader.getSheets();
            for (Sheet sheet : sheets) {
                reader.read(sheet);
            }
            List<T> dataList = listener.getDataList();
            return dataList;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            return null;
        }
    }

    /**
     * 读Excel
     * 实体必须继承了BaseRowModel 并且字段上必须有@ExcelProperty这样的注解
     * ExcelProperty注解参数说明
     * value  表头值
     * index  列顺序(列顺序，越小越靠前)
     * format 日期格式化
     *
     * @param filePath Excel文件地址
     * @param clazz    必须是继承了BaseRowModel的对象的class
     * @return Excel数据
     */
    public static <T> List<T> modelReadExcel(String filePath, Class<T> clazz) {
        if (filePath == null) {
            return null;
        }
        if (!BaseRowModel.class.isAssignableFrom(clazz)) {
            return null;
        }
        InputStream is = null;
        try {
            is = new FileInputStream(filePath);
            ModelExcelListener<T> listener = new ModelExcelListener<>();
            ExcelReader reader = new ExcelReader(is, null, listener);
            List<Sheet> sheets = reader.getSheets();
            for (Sheet sheet : sheets) {
                sheet.setClazz((Class<? extends BaseRowModel>) clazz);
                reader.read(sheet);
            }
            List<T> dataList = listener.getDataList();
            return dataList;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            return null;
        }
    }

    static class ModelExcelListener<T> extends AnalysisEventListener<T> {

        private List<T> dataList = new ArrayList<>();

        @Override
        public void invoke(T object, AnalysisContext context) {
            dataList.add(object);
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {

        }

        public List<T> getDataList() {
            return dataList;
        }
    }

    static class ExcelListener<T> extends AnalysisEventListener<List<String>> {

        private List<T> dataList = new ArrayList<>();

        private List headList;

        private boolean isExistHead;

        private Class<T> clazz;

        public ExcelListener(boolean isExistHead, Class<T> clazz) {
            this.isExistHead = isExistHead;
            this.clazz = clazz;
        }

        @Override
        public void invoke(List<String> object, AnalysisContext context) {
            int size = object.size();
            if (headList == null) {
                if (isExistHead) {
                    // 有头信息的情况
                    if (context.getCurrentRowNum() == 0) {
                        if (Map.class.isAssignableFrom(clazz) || Map.class == clazz) {
                            headList = object;
                        } else {
                            headList = initFieldHead();
                        }
                        return;
                    }
                } else {
                    // 没有头信息 先将头列表初始化出来
                    if (Map.class.isAssignableFrom(clazz) || Map.class == clazz) {
                        headList = new ArrayList<>();
                        for (int i = 0; i < size; i++) {
                            headList.add("c" + i);
                        }
                    } else {
                        headList = initFieldHead();
                    }
                }
            }
            if (Map.class.isAssignableFrom(clazz) || Map.class == clazz) {
                dataList.add((T) changeMap(object));
            } else {
                dataList.add(changeModel(object));
            }
        }

        /**
         * 初始化字段头列表
         */
        private List initFieldHead() {
            List headList = new ArrayList<>();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                headList.add(field);
            }
            return headList;
        }

        /**
         * 转换为Model
         *
         * @param object 行信息
         * @return clazz的实例化对象
         */
        private T changeModel(List<String> object) {
            try {
                T t = clazz.newInstance();
                for (int i = 0; i < headList.size(); i++) {
                    Field field = (Field) headList.get(i);
                    executeSetMethod(t, field, strToObj(object.get(i), field.getType()));
                }
                return t;
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
                return null;
            }
        }

        /**
         * 转换为Map
         *
         * @param object 行信息
         * @return LinkedHashMap对象
         */
        private Map<String, Object> changeMap(List<String> object) {
            int size = headList.size();
            Map<String, Object> map = new LinkedHashMap<>(size);
            for (int i = 0; i < size; i++) {
                String key = (String) headList.get(i);
                map.put(key, object.get(i));
            }
            return map;
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {

        }

        public List<T> getDataList() {
            return dataList;
        }
    }

}
